package campfire

import (
	"code.google.com/p/marius/jsonhttp"
	"errors"
	"fmt"
	"log"
)

type Campfire struct {
	token  string
	Realm  string
	client *jsonhttp.Client
}

type Room struct {
	*Campfire `json:"-"`
	Name      string
	Topic     string
	Id        int
	Users     []*User
}

type Msg struct {
	*Campfire `json:"-"`
	Body      *string
	Type      *string
	CreatedAt string `json:"created_at"`
	UserId    *int   `json:"user_id"`
}

type FullMsg struct {
	*Msg
	User *User
}

type User struct {
	Id        int
	Name      *string
	Email     *string `json:"email_address"`
	AvatarUrl *string `json:"avatar-url"`
}

type userWrap struct {
	User User
}

type roomWrap struct {
	Rooms []*Room
}

type roomWrap1 struct {
	Room *Room
}

type msgs struct {
	Messages []*Msg
}

type speakMsg struct {
	Type string `json:"type"`
	Body string `json:"body"`
}

type speakMsgWrap struct {
	Message speakMsg `json:"message"`
}

var userCache map[int]*User = make(map[int]*User)

func (cf *Campfire) url(uri string, args ...interface{}) string {
	uri = fmt.Sprintf(uri, args...)
	return fmt.Sprintf("https://%s.campfirenow.com/%s", cf.Realm, uri)
}

func New(token, realm string) (*Campfire, error) {
	client := jsonhttp.New()
	client.SetBasicAuth(token, "x")
	return &Campfire{token, realm, client}, nil
}

func (cf *Campfire) GetRooms() ([]*Room, error) {
	var rw roomWrap
	err := cf.client.Get(nil, &rw, cf.url("rooms.json"))
	if err != nil {
		return nil, err
	}

	rooms := rw.Rooms
	if rooms == nil {
		return nil, errors.New("rooms missing")
	}

	for _, r := range rooms {
		r.Campfire = cf
	}

	return rooms, nil
}

func (cf *Campfire) GetRoom(id int) (*Room, error) {
	var r1 roomWrap1
	err := cf.client.Get(nil, &r1, cf.url("room/%d.json", id))
	if err != nil {
		return nil, err
	}

	if r1.Room == nil {
		return nil, errors.New("missing room")
	}

	r := r1.Room
	r.Campfire = cf
	return r, nil
}

func (cf *Campfire) GetUser(id int) (*User, error) {
	if u, ok := userCache[id]; ok {
		return u, nil
	}

	var uw userWrap
	err := cf.client.Get(nil, &uw, cf.url("users/%d.json", id))
	if err != nil {
		return nil, err
	}

	userCache[id] = &uw.User
	return &uw.User, nil
}

func (r *Room) String() string {
	return fmt.Sprintf("%s <%s>", r.Name, r.Topic)
}

func (r *Room) join() error {
	return r.client.Post(nil, nil, r.url("room/%d/join.xml", r.Id))
}

func (r *Room) Transcript() <-chan *FullMsg {
	ch := make(chan *FullMsg)

	go func() {
		defer close(ch)
		var msgs msgs
		err := r.client.Get(nil, &msgs, r.url("room/%d/transcript.json", r.Id))
		if err != nil {
			log.Print(err)
			return
		}
		for i := range msgs.Messages {
			msg := msgs.Messages[i]
			msg.Campfire = r.Campfire
			ch <- msg.fill()
		}
	}()

	return ch
}

func (r *Room) Speak(body string) error {
	msg := speakMsg{"TextMessage", body}
	wrap := speakMsgWrap{msg}
	return r.client.Post(wrap, nil, r.url("room/%d/speak.json", r.Id))
}

func (r *Room) MonitorFull() (<-chan *FullMsg, error) {
	ch, err := r.Monitor()
	if err != nil {
		return nil, err
	}

	fullCh := make(chan *FullMsg)
	go func() {
		for m := range ch {
			fullCh <- m.fill()
		}
		close(fullCh)
	}()

	return fullCh, nil
}

func (r *Room) Monitor() (<-chan *Msg, error) {
	err := r.join()
	if err != nil {
		return nil, err
	}

	url := fmt.Sprintf("https://streaming.campfirenow.com/room/%d/live.json", r.Id)
	ch, err := r.client.DoStream(nil, Msg{}, '\r', "GET", url)
	if err != nil {
		return nil, err
	}

	castch := make(chan *Msg)
	go func() {
		defer close(castch)
		for v := range ch {
			m := v.(*Msg)
			m.Campfire = r.Campfire
			castch <- m
		}
	}()

	return castch, nil
}

func (m *Msg) fill() *FullMsg {
	var user *User
	if m.UserId != nil {
		var err error
		user, err = m.GetUser(*m.UserId)
		if err != nil {
			log.Print(err)
		}
	}
	return &FullMsg{m, user}
}
